<?php

/**
 * World Weather Online
 *
 * @copyright  Copyright (c) 2011 Celebrio Software
 * @package    WeatherApi
 */
namespace Celebrio\WeatherApi\WorldWeatherOnline;

use \SimpleXMLIterator;
use \InvalidArgumentException;

use Nette\Object;

use Celebrio\WeatherApi\WorldWeatherOnline\CurrentConditions;
use Celebrio\WeatherApi\WorldWeatherOnline\ForecastConditions;
use Celebrio\WeatherApi\WorldWeatherOnline\AreaInformations;
use Celebrio\WeatherApi\WorldWeatherOnline\LocationNotFoundException;
use Celebrio\Util\RequestBuilder;

class WorldWeatherOnline extends Object{
    
    const QUERY_BASIC_LINK = "http://free.worldweatheronline.com/feed/weather.ashx?";
    
    public static $api_key;
    
    private $place;
    private $latitude;
    private $longitude;
    private $requestLink = "";
    private $format;
    private $numOfDays = 5;
    
    public function __construct($latitude = null, $longitude = null, $place = null) {
        $this->requestLink = self::QUERY_BASIC_LINK;
        $this->setPosition($latitude, $longitude);
        $this->setPlace($place);
        $this->format = "xml";
    }
    
    public function getForecast() {
        $query = $this->composeQuery();
        
        $response = "<?xml version='1.0' encoding='UTF-8'?><data><request><type>LatLon</type><query>Lat 49.26 and Lon 16.59</query></request><current_condition><observation_time>08:13 PM</observation_time><temp_C>-1</temp_C><temp_F>30</temp_F><weatherCode>143</weatherCode><weatherIconUrl><![CDATA[http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0006_mist.png]]></weatherIconUrl><weatherDesc><![CDATA[Mist]]></weatherDesc><windspeedMiles>6</windspeedMiles><windspeedKmph>9</windspeedKmph><winddirDegree>60</winddirDegree><winddir16Point>ENE</winddir16Point><precipMM>0.0</precipMM><humidity>86</humidity><visibility>4</visibility><pressure>1026</pressure><cloudcover>0</cloudcover></current_condition><weather><date>2011-11-16</date><tempMaxC>4</tempMaxC><tempMaxF>38</tempMaxF><tempMinC>-3</tempMinC><tempMinF>26</tempMinF><windspeedMiles>6</windspeedMiles><windspeedKmph>9</windspeedKmph><winddirection>NE</winddirection><winddir16Point>NE</winddir16Point><winddirDegree>52</winddirDegree><weatherCode>113</weatherCode><weatherIconUrl><![CDATA[http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0001_sunny.png]]></weatherIconUrl><weatherDesc><![CDATA[Sunny]]></weatherDesc><precipMM>0.0</precipMM></weather><weather><date>2011-11-17</date><tempMaxC>4</tempMaxC><tempMaxF>40</tempMaxF><tempMinC>-3</tempMinC><tempMinF>26</tempMinF><windspeedMiles>7</windspeedMiles><windspeedKmph>12</windspeedKmph><winddirection>SSE</winddirection><winddir16Point>SSE</winddir16Point><winddirDegree>168</winddirDegree><weatherCode>113</weatherCode><weatherIconUrl><![CDATA[http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0001_sunny.png]]></weatherIconUrl><weatherDesc><![CDATA[Sunny]]></weatherDesc><precipMM>0.0</precipMM></weather><weather><date>2011-11-18</date><tempMaxC>4</tempMaxC><tempMaxF>40</tempMaxF><tempMinC>-5</tempMinC><tempMinF>23</tempMinF><windspeedMiles>3</windspeedMiles><windspeedKmph>5</windspeedKmph><winddirection>SSW</winddirection><winddir16Point>SSW</winddir16Point><winddirDegree>202</winddirDegree><weatherCode>113</weatherCode><weatherIconUrl><![CDATA[http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0001_sunny.png]]></weatherIconUrl><weatherDesc><![CDATA[Sunny]]></weatherDesc><precipMM>0.0</precipMM></weather><weather><date>2011-11-19</date><tempMaxC>5</tempMaxC><tempMaxF>42</tempMaxF><tempMinC>-4</tempMinC><tempMinF>25</tempMinF><windspeedMiles>3</windspeedMiles><windspeedKmph>5</windspeedKmph><winddirection>SSE</winddirection><winddir16Point>SSE</winddir16Point><winddirDegree>153</winddirDegree><weatherCode>113</weatherCode><weatherIconUrl><![CDATA[http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0001_sunny.png]]></weatherIconUrl><weatherDesc><![CDATA[Sunny]]></weatherDesc><precipMM>0.0</precipMM></weather><weather><date>2011-11-20</date><tempMaxC>4</tempMaxC><tempMaxF>40</tempMaxF><tempMinC>-5</tempMinC><tempMinF>23</tempMinF><windspeedMiles>3</windspeedMiles><windspeedKmph>5</windspeedKmph><winddirection>SSE</winddirection><winddir16Point>SSE</winddir16Point><winddirDegree>160</winddirDegree><weatherCode>116</weatherCode><weatherIconUrl><![CDATA[http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0002_sunny_intervals.png]]></weatherIconUrl><weatherDesc><![CDATA[Partly Cloudy ]]></weatherDesc><precipMM>0.0</precipMM></weather></data>";
//        $ch = curl_init($this->requestLink);
//
//        curl_setopt($ch, CURLOPT_HEADER, 0);
//        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
//        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
//
//        $response = curl_exec($ch);
//        $info = curl_getinfo($ch);
//        //TODO: Předělat, at se místo výjimky zpracuje nacachovaná hodnota a ukáže uživateli + nějaký flag nedostupnosti?
//        if($info["http_code"] == 500) {
//            throw new ServerNotRespondingException("Server is not responding");
//        }
        $document = new SimpleXMLIterator($response);
        $forecast = $this->performOutputXML($document);
        return $forecast;
    }
    
    private function composeQuery() {
        $this->specifyPlace();
        $this->specifyPrivateKey();
        $this->specifyNumOfDays($this->numOfDays);
        $this->specifyFormat();
    }
    
    
    private function performOutputXML(SimpleXMLIterator $document) {
        $error = $document->xpath("/data/error");
        if(!empty($error)) {
            $this->handleError($error[0]);
        }
        
        $output = array();
        
        $current = $document->xpath("/data/current_condition");        
        
        if(!empty($current)) {
            $currentConditions = $this->composeCurrentConditions($current[0]);
            $output["currentConditions"] = $currentConditions;
        }
        
        $weather = $document->xpath("/data/weather");
        
        if(!empty($weather)) {
            $forecast = array();
            foreach($weather as $conditions) {
                $composedForecast = $this->composeForecast($conditions);
                $forecast[] = $composedForecast;
            }
            $output["forecast"] = $forecast;
        }
        
        $locationData = $document->xpath("/data/nearest_area");
        if(!empty($locationData)) {
            $info = $this->composeAreaInformations($locationData[0]);
            $output["areaInfo"] = $info;
        }
        
        return $output;
        
    }
    
    private function composeAreaInformations(SimpleXMLIterator $locationData) {
        $informations = new AreaInformations();
        
        foreach($locationData as $info) {
            $informations->setAttribute($info->getName(), trim($info->__toString()));
        }
        return $informations;
    }
    
    private function composeCurrentConditions(SimpleXMLIterator $currentWeather) {
        $current = new CurrentConditions();
        foreach($currentWeather->children() as $attribute) {
            $current->setAttribute($attribute->getName(), trim($attribute->__toString()));
        }
        return $current;
    }
    
    private function composeForecast(SimpleXMLIterator $weather) {
        $forecast = new ForecastConditions();
        foreach($weather->children() as $child) {
            $forecast->setAttribute($child->getName(), trim($child->__toString()));
        }
        
        return $forecast;
    }
    
    
    private function specifyPlace() {
        if(($this->latitude == null && $this->longitude == null) && $this->place == null) {
            throw new \InvalidStateException("There are not enaugh parameters to determine location");
        }
        if(empty($this->place)) {
            $this->append("q=" . $this->latitude . "," . $this->longitude);
        } else {
            $this->append("q=" . $this->place);
        }
        
    }
    
    private function specifyNumOfDays($numOfDays = "2") {
        $this->append("num_of_days=" . $numOfDays);
    }
    
    private function specifyPrivateKey($privateKey = null) {
        if(empty($privateKey)) {
            $privateKey = self::$api_key;
        }
        $this->append("key=" . $privateKey);
    }
    
    //TODO : Implement
    private function specifyDate($date = null) {
        throw new \NotImplementedException("This functionality will be available soon");
    }
    
    /**
     * Can be "xml", "json", "csv". Default is xml
     * At this time only available and supported is xml from our side
     * 
     * @param type $format in which data will be returned
     */
    private function specifyFormat() {
        if(empty($this->format)) {
            $this->format = "xml";
        }
        
        //TODO: After finishing support for json and csv remove next line.
        $this->format = "xml";
        
        $this->append("format=" . $this->format);
    }
    
    /**
     * Enable include of location of nearest weather point. Default is no
     */
    private function enableLocation() {
        $this->append("includeLocation=yes");
    }
    
    /**
     * Disable comments. Default is yes.
     */
    private function disableComments() {
        $this->append("show_comments=no");
    }
    
    private function disableNormalOutput() {
        $this->append("fx=no");
    }
    
    private function  disableCurrentConditionsOutput() {
        $this->append("fx=no");
    }


    private function handleError($error) {
        $notFound = "Unable to find any matching weather location to the query submitted!";
        $errorType = $error->error->__toString();
        switch ($errorType) {
            case $notFound:
                throw new LocationNotFoundException(_("Unable to find any matching weather location"));
                break;

            default:
                break;
        }
    }

    private function append($appendString) {
        //if last character is not & or ? we add & to the end of string
        if(!in_array(substr($this->requestLink, -1, 1), array("&", "?"))) {
            $this->requestLink .= "&";
        }
        $this->requestLink .= $appendString;
    }
    
    public function setPosition($latitude, $longitude) {
        $this->latitude = $latitude;
        $this->longitude = $longitude;
    }


    public function setPlace($place) {
        $this->place = $place;
    }
    
    public function setFormat($format) {
        if(!in_array($format, array("xml", "json", "csv"))) {
            throw new \InvalidArgumentException("This format ($format) is not supported.");
        } else {
            $this->format = $format;
        }
    }
}